home *** CD-ROM | disk | FTP | other *** search
- /* TwistDownList.c */
- /*
- * List In A List Sample
- * TwistDownList.c
- * Copyright © 1993-94 Apple Computer Inc. All rights reserved.
- *
- * TwistDownList manages all aspects of the "twist-down" list, a one-column text
- * list where each row contains a triangular button. The button may be in one of
- * two states: closed and opened. When opened, sub-elements to this row are
- * displayed. The twist-down list is generally based on code from NewsWatcher
- * by Steve Falkenburg, but Steve says that the code was originally written by
- * John Norstad.
- *
- * Applications must provide a LDEF stub resource by adding the following (or
- * something equivalent) to the resource definition file:
- * #ifndef LDEF_Stub
- * #define LDEF_Stub 1024
- * #endif
- * type 'LDEF' {
- * unsigned hex longint = $41FA0006; // lea pc+8,a0 ; a0 -> ProcPtr
- * unsigned hex int = $2050; // movea.l (a0),a0 ; a0 -> LDEF
- * unsigned hex int = $4ED0; // jmp (a0) ; jump to it
- * unsigned hex longint = 0; // dc.l 0 ; LDEF ProcPtr
- * };
- * resource 'LDEF' (LDEF_Stub, "Stub LDEF", preload, locked) { };
- *
- * Because of the way that the callback LDEF is managed, TwistDownList.c must be
- * in the application's root segment. If this is inappropriate, the segement
- * resource must be marked "preload" and "locked" -- otherwise, your program will
- * crash. (Note that this applies only to 68000 code.)
- */
- #include <stdio.h>
- #include "TwistDownList.h"
- #include "Palettes.h"
- #include "IntlResources.h"
- #include "Script.h"
- #include "TextUtils.h"
- #include "SegLoad.h"
- #include "FixMath.h"
- #include "ToolUtils.h"
- #include "Gestalt.h"
- #include "Resources.h"
- #include "Packages.h"
- #ifdef __powerc
- /*
- * PowerPC System area locations in "Get" and "Set" functions. They are
- * defined in LowMem.h
- */
- #include <LowMem.h>
- #else
- #ifdef MPW
- /*
- * MPW requires a reference to a low memory global to set hiliting
- * Think C and MetroWorks includes SysEqu.h in the default MacHeaders file.
- */
- #include <SysEqu.h>
- #endif
- #endif
-
- #define UNUSED(what) do { \
- what; \
- } while (0)
-
- #ifndef MONOCHROME_FILL
- #define MONOCHROME_FILL 0
- #endif
-
- #define LIST (**theList)
- /*
- * A common List Selection flag variation that equates Shift and Command keys.
- */
- #define SELECTION_FLAGS \
- (lUseSense | lNoRect | lNoExtend | lNoNilHilite | lDoVAutoscroll)
- /*
- * SELECTION_FLAGS, by default, allows a single list cell to be selected.
- */
- #ifndef SELECTION_FLAGS
- #define SELECTION_FLAGS (lOnlyOne | lNoNilHilite | lDoVAutoscroll)
- #endif
-
- enum {
- kScrollBarWidth = 16,
- kScrollBarOffset = kScrollBarWidth - 1,
- kActiveControl = 0, /* Normal button, no hilite */
- kDisabledControl = 255, /* Disabled button hilite */
- kZeroIndent = 4 /* Indent if scrollbar == 0 */
- };
- /*
- * This is a nominal value for the maximum cell width. It might be better as a
- * user-settable parameter.
- */
- #define kMaxHorizontalScroll (CharWidth('M') * 255)
-
- /*
- * kAnimationDelay is the number of ticks to display the intermediate
- * "twisting" glyph.
- */
- #define kAnimationDelay 3L
- /*
- * The triangle gap parameters define the amount of space to display to the
- * left and right of the twist-down triangle. The "outside gap" is to the left
- * on Roman-alphabet scripts and on the right on Hebrew or Arabic scripts.
- * The default values are suitable for small font sizes, but could be increased
- * for large sizes.
- */
- #define kTriangleOutsideGap (1) /* From margin to button */
- #define kTriangleInsideGap (2) /* From button to text */
- #define kScrollBarWidth (16) /* Width of a scroll bar */
-
- /*
- * Parameters for the print handler.
- */
- #define kPrintoutHeaderFont "\pHelvetica"
- #define kPrintoutHeaderFontSize 9
- #define kPrintoutHeaderGap 4
- #define kPrintoutHeaderStyle bold
-
- /*
- * The List's userHandle contains our private context information. The PolyHandles
- * are used to draw the "triangle" buttons. Note that they are drawn to the list
- * cell height. When it changes, the triangles will be reconstructed. The fontSize,
- * fontNumber, and isLeftJustify variables are used to draw the list cell content.
- */
- struct TwistDownPrivateRecord {
- TwistDownDrawProc drawProc; /* This draws the cell data */
- PolyHandle openTriangle; /* The "expanded" button */
- PolyHandle closedTriangle; /* The "closed" button */
- PolyHandle intermediateTriangle; /* The "expanding" button */
- ControlHandle hScroll; /* List's horiz. scrollbar */
- short tabIndent; /* NewTwistDownList param */
- short fontSize; /* for TextSize */
- short fontNumber; /* for TextFont */
- Boolean canHiliteSelection; /* TRUE if hilite ok */
- Boolean isLeftJustify; /* GetSysJust value */
- short triangleWidth; /* Twist-down button width */
- };
- typedef struct TwistDownPrivateRecord TwistDownPrivateRecord,
- *TwistDownPrivatePtr, **TwistDownPrivateHdl;
-
- /*
- * The de-referenced private data record is too long to type each time it appears
- * so it will be defined by the PRIVATE macro.
- */
- #define PRIVATE (**((TwistDownPrivateHdl) (LIST.userHandle)))
- /*
- * These macros simplify access to the flag word in the list element.
- */
- #define SetTDFlag(elementHdl, mask) ((**elementHdl).flag |= (mask))
- #define ClearTDFlag(elementHdl, mask) ((**elementHdl).flag &= ~(mask))
- #define InvertTDFlag(elementHdl, mask) ((**elementHdl).flag ^= (mask))
- #define TestTDFlag(elementHdl, mask) (((**elementHdl).flag & (mask)) != 0)
-
- #define SIBLING (*twistDownSiblingSetPtr)
-
- /*
- * The proper way to build a compiled-in LDEF is to plug a transfer address into a
- * stub code resource. The StubRecord must track any changes in the LDEF resource.
- */
- #ifdef __powerc
- #pragma options align=mac68k
- #endif
- struct StubRecord {
- long lea; /* Lea (pc)+8,a0 */
- short movea; /* Movea.l (a0),a0 */
- short jmp; /* jmp (a0) */
- ListDefUPP ldefAddress; /* dc.l 0 */
- };
- typedef struct StubRecord **StubHandle;
- #ifdef __powerc
- #pragma options align=reset
- #endif
-
- /*
- * Local (private) functions.
- */
- /*
- * Dispose of a PolyHandle. This must be a macro. The argument
- * may not have side-effects.
- */
- #define ForgetPoly(thePoly) do { \
- if (thePoly != NULL) { \
- KillPoly(thePoly); \
- thePoly = NULL; \
- } \
- } while (0)
- #define height(r) ((r).bottom - (r).top)
- #define width(r) ((r).right - (r).left)
-
- static short CountVisibleElements(
- TwistDownHdl twistDownHandle
- );
- static void ClearSelectedElementBit(
- TwistDownHdl twistDownHandle
- );
- static void CopySelectionStateToList(
- ListHandle theList,
- short selectedRow
- );
- static void SetElementsInList(
- ListHandle theList,
- TwistDownHdl twistDownHandle,
- Cell *currentCell
- );
- static pascal void TwistDownLDEF(
- short listMessage,
- Boolean listSelect,
- Rect *listRect,
- Cell listCell,
- short listDataOffset,
- short listDataLen,
- ListHandle listHandle
- );
- static void DrawTriangle(
- PolyHandle polyHandle,
- Point polyPoint,
- Boolean isSelected
- );
- static pascal void DefaultTwistDownDrawProc(
- ListHandle theList, /* The list itself */
- TwistDownPtr twistDownPtr, /* Locked data handle */
- const Rect *viewRect /* Draw in this area */
- );
-
- /*
- * Horizontal scrollbar stuff
- */
- static void AdjustHorizontalScrollbar(
- ListHandle theList
- );
- static pascal void ScrollTwistDownActionProc(
- register ControlHandle theControl,
- short partCode
- );
- static void ScrollTwistDownList(
- register ControlHandle theControl
- );
-
- static void pstrcpy(
- StringPtr dst,
- ConstStr255Param src
- );
- static void pstrcat(
- StringPtr dst,
- ConstStr255Param src
- );
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * NewTwistDownList
- *
- * Create a twist-down list. Before calling, you must set the port to the current
- * window and specify the font and font size that is to be used to draw the list.
- * NewTwistDownList creates an empty one-column list with vertical and horizontal
- * scroll bars. Only one item may be selected at a time; but this could be changed
- * by the application without difficulty. The scrollbars are managed internally.
- *
- * Note: unlike the previous release of TwistDownList, the viewRect does not
- * include the scroll bars. For a fullscreen list, do the following:
- * viewRect = FrontWindow()->portRect;
- * viewRect.right -= kScrollBarWidth;
- * viewRect.bottom -= kScrollBarWidth;
- *
- * The tabIndent parameter should be set to the amount to indent successive levels
- * (zero means no indentation). Setting it to the widMax value from the current
- * font seems reasonable.
- *
- * canHiliteSelection should be TRUE for normal selection (the selection is
- * hilited). It should be FALSE if you want to supress selection. Because
- * hirearchical lists often "select" by revealing a sub-topic, this may be
- * more reasonable in many cases.
- *
- * isLeftJustify should be set TRUE for systems using the Roman alphabet. It would
- * be set FALSE for right-to-left languages such as Arabic and Hebrew. It is used
- * to configure the direction of the buttons and the location of text within
- * the displayed list cell.
- *
- * We manage the horizontal scrollbar by some private trickery -- this lets the
- * list pane be smaller than the amount of text to be displayed.
- */
- pascal ListHandle
- NewTwistDownList(
- const Rect *viewRect,
- TwistDownDrawProc drawProc,
- unsigned short tabIndent,
- Boolean canHiliteSelection,
- Boolean isLeftJustify,
- Boolean hasGrowBox
- )
- {
- register TwistDownPrivatePtr privatePtr;
- ListHandle theList;
- short listHeight;
- Point cellSize;
- Rect dataBounds;
- Rect listRect;
- FontInfo info;
- short listFontHeight;
- GrafPtr currentPort;
- ListDefUPP listDefProc;
- StubHandle stubHandle;
-
- theList = NULL;
- GetPort(¤tPort);
- GetFontInfo(&info);
- listFontHeight = info.ascent + info.descent + info.leading;
- /*
- * Define the list drawing area. If the list viewRect.bottom
- * is less than the portRect.bottom, adjust the list area height
- * integral number of rows will be drawn. If equal, the list
- * area abuts the bottom of the display window and we shouldn't
- * change the bottom or the scroll bars will look wierd.
- */
- listRect = *viewRect;
- SetPt(&cellSize, width(listRect), listFontHeight);
- if ((listRect.bottom + kScrollBarWidth) < currentPort->portRect.bottom) {
- listHeight = height(listRect);
- listHeight -= (listHeight % listFontHeight);
- listRect.bottom = listRect.top + listHeight;
- }
- /*
- * Define a one-column list.
- */
- listDefProc = NewListDefProc(TwistDownLDEF);
- stubHandle = (StubHandle) GetResource('LDEF', LDEF_Stub);
- if (stubHandle == NULL)
- goto exit; /* Failure */
- (**stubHandle).ldefAddress = listDefProc;
- HNoPurge((Handle) stubHandle);
- SetRect(&dataBounds, 0, 0, 1, 0);
- theList = LNew(
- &listRect, /* Viewing area */
- &dataBounds, /* Rows and col's */
- cellSize, /* Element size */
- LDEF_Stub, /* Callback defproc */
- currentPort, /* Display window */
- TRUE, /* Draw it */
- hasGrowBox, /* Grow box if TRUE */
- TRUE, /* Horizontal scroll */
- TRUE /* Vertical scroll */
- );
- if (theList == NULL)
- goto exit;
- LIST.selFlags = SELECTION_FLAGS;
- LIST.refCon = 0; /* Paranoia */
- LIST.userHandle = NewHandleClear(sizeof (TwistDownPrivateRecord));
- if (LIST.userHandle == NULL)
- goto failure;
- privatePtr = (TwistDownPrivatePtr) (*LIST.userHandle);
- #define PRIV (*privatePtr)
- PRIV.drawProc = (drawProc != NULL) ? drawProc : DefaultTwistDownDrawProc;
- PRIV.tabIndent = tabIndent;
- PRIV.canHiliteSelection = canHiliteSelection;
- PRIV.isLeftJustify = isLeftJustify;
- PRIV.fontNumber = currentPort->txFont;
- PRIV.fontSize = currentPort->txSize;
- /*
- * Grab the horizontal scrollbar and clear it from the list record then
- * link the horizontal scrollbar back to the list, and configure it.
- */
- PRIV.hScroll = LIST.hScroll;
- LIST.hScroll = NULL;
- SetControlReference(PRIV.hScroll, (long) theList);
- AdjustHorizontalScrollbar(theList);
- #undef PRIV
- CreateTwistDownButtons(theList);
- goto exit;
- failure:
- DisposeTwistDownList(theList);
- theList = NULL;
- exit: return (theList);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DisposeTwistDownList
- *
- * Dispose of the list and our private data.
- */
- pascal void
- DisposeTwistDownList(
- ListHandle theList
- )
- {
- if (theList != NULL) {
- if (LIST.userHandle != NULL) {
- LIST.hScroll = PRIVATE.hScroll;
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- DisposeHandle((Handle) LIST.userHandle);
- LIST.userHandle = NULL;
- }
- LDispose(theList);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * SetTwistDownListFont
- *
- * Change the display font and font size
- */
- pascal void
- SetTwistDownListFont(
- ListHandle theList,
- short fontNumber,
- short fontSize
- )
- {
- FontInfo info;
- Rect listRect;
- Point cellSize;
-
- PRIVATE.fontNumber = fontNumber;
- PRIVATE.fontSize = fontSize;
- SetPort(LIST.port);
- TextFont(PRIVATE.fontNumber);
- TextSize(PRIVATE.fontSize);
- GetFontInfo(&info);
- listRect = LIST.rView;
- cellSize.h = width(listRect);
- cellSize.v = info.ascent + info.descent + info.leading;
- LIST.indent.v = info.ascent;
- LCellSize(cellSize, theList);
- CreateTwistDownButtons(theList);
- InvalRect(&listRect);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * UpdateTwistDownList
- * Explicitly update the twistdown list. This is generally called only after
- * a Modal Dialog or Alert obscured the window.
- */
- pascal void
- UpdateTwistDownList(
- ListHandle theList
- )
- {
- WindowPtr theWindow;
- GrafPtr savePort;
- Rect viewRect;
- RgnHandle listRgn;
- RgnHandle clipRgn;
-
- if (theList != NULL) {
- theWindow = (WindowPtr) LIST.port;
- if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
- viewRect = LIST.rView;
- if (LIST.hScroll != NULL || PRIVATE.hScroll != NULL)
- viewRect.bottom += kScrollBarWidth;
- if (LIST.vScroll != NULL)
- viewRect.right += kScrollBarWidth;
- listRgn = NewRgn();
- RectRgn(listRgn, &viewRect);
- SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
- if (EmptyRgn(listRgn) == FALSE) {
- /*
- * Fake an update event handler
- */
- GetPort(&savePort);
- SetPort(theWindow);
- clipRgn = NewRgn();
- GetClip(clipRgn);
- SetClip(listRgn);
- EraseRgn(listRgn);
- DrawControls(theWindow);
- DrawGrowIcon(theWindow);
- InsetRect(&viewRect, -1, -1);
- FrameRect(&viewRect);
- LUpdate(listRgn, theList);
- DisposeRgn(clipRgn);
- ValidRgn(listRgn);
- SetPort(savePort);
- }
- DisposeRgn(listRgn);
- }
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * MoveTwistDownList
- * Move the list within the window.
- */
- pascal void
- MoveTwistDownList(
- ListHandle theList,
- short leftEdge,
- short topEdge
- )
- {
- Rect viewRect;
-
- if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
- viewRect = LIST.rView;
- InsetRect(&viewRect, -1, -1);
- InvalRect(&viewRect);
- OffsetRect(
- &LIST.rView,
- leftEdge - LIST.rView.left,
- topEdge - LIST.rView.top
- );
- viewRect = LIST.rView;
- InsetRect(&viewRect, -1, -1);
- InvalRect(&viewRect);
- MoveControl(
- LIST.vScroll,
- LIST.rView.right - kScrollBarOffset,
- LIST.rView.top - 1
- );
- MoveControl(
- PRIVATE.hScroll,
- LIST.rView.left - 1,
- LIST.rView.bottom - kScrollBarOffset
- );
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * SizeTwistDownList
- * Resize the list. The viewRect is the list rectangle size and does not include
- * the scrollbars. To create a full-screen list, pass the window's portRect with
- * the scrollbars removed.
- */
- pascal void
- SizeTwistDownList(
- ListHandle theList,
- short newWidth,
- short newHeight
- )
- {
- Rect viewRect;
- Point cellSize;
- GrafPtr currentPort;
- GrafPtr listPort;
-
- GetPort(¤tPort);
- listPort = LIST.port;
- SetPort(listPort);
- viewRect = LIST.rView;
- InsetRect(&viewRect, -1, -1);
- InvalRect(&viewRect);
- /*
- * Put the horizontal scrollbar back into the list record so that
- * the list manager does the resizing for us.
- */
- LIST.hScroll = PRIVATE.hScroll;
- LSize(newWidth, newHeight, theList);
- LIST.hScroll = NULL;
- cellSize = LIST.cellSize;
- cellSize.h = width(LIST.rView);
- LCellSize(cellSize, theList);
- AdjustHorizontalScrollbar(theList);
- viewRect = LIST.rView;
- InsetRect(&viewRect, -1, -1);
- InvalRect(&viewRect);
- SetPort(currentPort);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CreateTwistDownButtons
- *
- * CreateTwistDownButtons
- * This function creates the three states of the twist-down button that are drawn
- * in the display list.
- *
- * CreateTwistDownButtons is called when the list is created. The application must
- * call it directly after changing the list cell height (by calling LSize).
- *
- * Note that the port and text drawing characteristics must have been set.
- */
- pascal void
- CreateTwistDownButtons(
- ListHandle theList
- )
- {
- short buttonSize;
- short halfSize;
- short intermediateSize;
- FontInfo info;
-
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- GetFontInfo(&info);
- buttonSize = info.ascent; /* The "show sublist" button */
- buttonSize &= ~1; /* Round down to an even number */
- halfSize = buttonSize / 2;
- intermediateSize = (buttonSize * 3) / 4;
- PRIVATE.openTriangle = OpenPoly();
- MoveTo(0, halfSize);
- LineTo(buttonSize, halfSize);
- LineTo(halfSize, buttonSize);
- LineTo(0, halfSize);
- ClosePoly();
- if (PRIVATE.isLeftJustify) { /* Roman alphabet triangles */
- PRIVATE.closedTriangle = OpenPoly();
- MoveTo(halfSize, 0);
- LineTo(buttonSize, halfSize);
- LineTo(halfSize, buttonSize);
- LineTo(halfSize, 0);
- ClosePoly();
- PRIVATE.intermediateTriangle = OpenPoly();
- MoveTo(intermediateSize, 0);
- LineTo(intermediateSize, intermediateSize);
- LineTo(0, intermediateSize);
- LineTo(intermediateSize, 0);
- ClosePoly();
- }
- else { /* Arabic/Hebrew triangles */
- PRIVATE.closedTriangle = OpenPoly();
- MoveTo(buttonSize - halfSize, 0);
- LineTo(0, halfSize);
- LineTo(buttonSize - halfSize, buttonSize);
- LineTo(buttonSize - halfSize, 0);
- ClosePoly();
- PRIVATE.intermediateTriangle = OpenPoly();
- MoveTo(buttonSize - intermediateSize, 0);
- LineTo(buttonSize - intermediateSize, intermediateSize);
- LineTo(buttonSize, intermediateSize);
- LineTo(buttonSize - intermediateSize, 0);
- ClosePoly();
- }
- /*
- * Remember the width of the "button" area.
- */
- PRIVATE.triangleWidth =
- (**PRIVATE.openTriangle).polyBBox.right
- + kTriangleOutsideGap
- + kTriangleInsideGap;
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DoTwistDownClick
- *
- * DoTwistDownClick handles all processing after a click in the list window.
- * It returns an indication of the user action:
- * kTwistDownNotInList
- * The click was not in this list. Your application may ignore this click or
- * take other appropriate action.
- * kTwistDownNoClick
- * The user released the mouse outside of the list area: this click should be
- * ignored. (It may have been a click in the scroll bar.)
- * kTwistDownButtonClick
- * The user clicked on the twist-down button. The list will be expanded or
- * contracted as appropriate.
- * kTwistDownClick
- * The user clicked (once) on a list datum. The application should treat this
- * as an item selection.
- * kTwistDownDoubleClick
- * The user double-clicked on a list datum. The application should open this
- * item or take other appropriate action.
- *
- * If DoTwistDownClick returns kTwistDownButtonClick, kTwistDownClick, or
- * kTwistDownDoubleClick, selectedListCell will be set to the cell that the user
- * clicked on.
- */
- pascal TwistDownClickState
- DoTwistDownClick(
- ListHandle theList,
- const EventRecord *eventRecordPtr,
- Cell *selectedListCell
- )
- {
- Cell theCell;
- Rect hitRect;
- Boolean inHitRect;
- Boolean newInHitRect;
- short cellHeight;
- short visibleTop;
- TwistDownHdl twistDownHandle;
- Point mousePt;
- TwistDownClickState result;
- long finalTicks;
- short part;
- ControlHandle theControl;
- static ControlActionUPP controlActionUPP;
-
- mousePt = eventRecordPtr->where;
- GlobalToLocal(&mousePt);
- /*
- * We handle the horizontal scrollbar ourselves -- and do not pass
- * these clicks to LClick as the scrollbar isn't there anymore.
- */
- hitRect = (**PRIVATE.hScroll).contrlRect;
- if (PtInRect(mousePt, &hitRect)) {
- part = FindControl(
- mousePt,
- (**PRIVATE.hScroll).contrlOwner,
- &theControl
- );
- if (part >= 0 && theControl == PRIVATE.hScroll) {
- if (part == kInIndicatorControlPart) {
- if (TrackControl(theControl, mousePt, NULL))
- ScrollTwistDownList(theControl);
- }
- else {
- if (controlActionUPP == NULL) {
- controlActionUPP = NewControlActionProc(
- ScrollTwistDownActionProc
- );
- }
- TrackControl(theControl, mousePt, controlActionUPP);
- }
- }
- }
- else {
- hitRect = LIST.rView;
- hitRect.right += kScrollBarWidth;
- if (PtInRect(mousePt, &hitRect) == FALSE)
- result = kTwistDownNotInList;
- else {
- /*
- * Set hitRect to the area of the list that contains the
- * twist-down triangle button. Note that this presumes a list
- * where only column zero is displayed.
- */
- if (PRIVATE.isLeftJustify) {
- hitRect.right = LIST.rView.left
- + LIST.indent.h
- + PRIVATE.triangleWidth;
- }
- else {
- hitRect.left = LIST.rView.right
- - LIST.indent.h
- - PRIVATE.triangleWidth;
- }
- inHitRect = FALSE;
- if (PtInRect(mousePt, &hitRect)) {
- /*
- * It's in a the triangle area. Find the selected cell and
- * check whether this cell's element has a visible button.
- */
- visibleTop = LIST.visible.top;
- cellHeight = LIST.cellSize.v;
- theCell.h = 0; /* This has the visual content */
- theCell.v =
- ((mousePt.v - LIST.rView.top) / cellHeight)
- + visibleTop;
- /*
- * Set inHitRect TRUE if there is a sub-list button here.\
- * Note: it is possible to have a button but no actual
- * sub-list (consider an empty folder in a disk hierarchy: the
- * presence of the button tells the user "it's a folder").
- */
- twistDownHandle = GetTwistDownElementHandle(theList, theCell);
- if (twistDownHandle != NULL
- && TestTDFlag(twistDownHandle, kHasTwistDown))
- inHitRect = TRUE;
- }
- if (inHitRect == FALSE) {
- /*
- * This cell doesn't have an expansion triangle, or the user
- * did not click in the button area. Just call the normal
- * list click handler to manage the scroll bars. Set result
- * appropriately.
- */
- result = (LClick(mousePt, eventRecordPtr->modifiers, theList))
- ? kTwistDownDoubleClick
- : kTwistDownClick;
- }
- else {
- /*
- * The user clicked on in the twist-down button area. Simulate
- * a button click and track the mouse as it wanders in and out
- * of the button area. (inHitRect is true at this point).
- * Whenever the button selection state changes, call LDraw to
- * redraw the twist-down triangle. This is the way to simulate
- * TrackControl.
- */
- SetTDFlag(twistDownHandle,
- (kDrawButtonFilled | kOnlyRedrawButton));
- LDraw(theCell, theList);
- /*
- * Set hitRect to the dimensions of the twist-down button.
- */
- hitRect.top =
- ((theCell.v - visibleTop) * cellHeight)
- + LIST.rView.top;
- hitRect.bottom = hitRect.top + cellHeight;
- /*
- * Track the mouse while it still down: if it moves into the
- * triangle rectangle, redraw it filled, if it moves out of
- * the triangle, redraw it unfilled.
- */
- if (StillDown()) {
- while (WaitMouseUp()) {
- GetMouse(&mousePt);
- newInHitRect = PtInRect(mousePt, &hitRect);
- if (newInHitRect != inHitRect) {
- /*
- * The mouse moved in or out of the triangle.
- */
- InvertTDFlag(twistDownHandle, kDrawButtonFilled);
- LDraw(theCell, theList);
- inHitRect = newInHitRect;
- }
- }
- }
- /*
- * The user released the mouse.
- */
- if (inHitRect == FALSE) {
- /*
- * Normally, drawButtonFilled will be clear. It can be set,
- * however, if the user clicks so briefly on the triangle
- * that the StillDown() test above is FALSE.
- */
- if (TestTDFlag(twistDownHandle, kDrawButtonFilled)) {
- ClearTDFlag(twistDownHandle, kDrawButtonFilled);
- LDraw(theCell, theList);
- }
- result = kTwistDownNoClick;
- }
- else {
- /*
- * The user released the mouse in the expansion triangle.
- * Draw an intermediate "animation" triangle. Then call
- * ExpandOrCollapseTwistDownList which will redraw the
- * button in its new state.
- */
- SetTDFlag(twistDownHandle,
- (kDrawIntermediate | kEraseButtonArea));
- LDraw(theCell, theList);
- Delay(kAnimationDelay, &finalTicks);
- ClearTDFlag(
- twistDownHandle,
- ( kDrawIntermediate
- | kDrawButtonFilled
- | kEraseButtonArea
- )
- );
- ExpandOrCollapseTwistDownList(theList, theCell);
- result = kTwistDownButtonClick;
- *selectedListCell = theCell;
- }
- ClearTDFlag(twistDownHandle, kOnlyRedrawButton);
- }
- }
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ExpandOrCollapseTwistDownList
- *
- * ExpandOrCollapseTwistDownList modifies the "show sublist" flag for the selected
- * cell and rebuilds the visual display.
- */
- pascal void
- ExpandOrCollapseTwistDownList(
- ListHandle theList,
- Cell selectedListCell
- )
- {
- TwistDownHdl twistDownHandle;
-
- twistDownHandle = GetTwistDownElementHandle(theList, selectedListCell);
- if (twistDownHandle != NULL
- && TestTDFlag(twistDownHandle, kHasTwistDown)) {
- InvertTDFlag(twistDownHandle, kShowSublist);
- /*
- * Redraw the twist-down button in its new state.
- */
- ClearTDFlag(twistDownHandle, kDrawButtonFilled);
- SetTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
- LDraw(selectedListCell, theList);
- ClearTDFlag(twistDownHandle, (kOnlyRedrawButton | kEraseButtonArea));
- /*
- * If some other part of the list will change, rebuild the
- * List Manager list cells and redraw the list.
- */
- if ((**twistDownHandle).subElement != NULL)
- BuildVisibleList(theList, selectedListCell.v);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CreateVisibleList
- *
- * CreateVisibleList is called when the list is created. It stores the list
- * head into cell [0, 0] and calls BuildVisibleList to instantiate the display.
- */
- pascal void
- CreateVisibleList(
- ListHandle theList,
- TwistDownHdl twistDownHandle
- )
- {
- Cell theCell;
-
- if (LIST.dataBounds.bottom == 0) {
- /*
- * Add one row to the list so there is a place for the head element.
- */
- LSetDrawingMode(FALSE, theList);
- LAddRow(1, 0, theList);
- LSetDrawingMode(TRUE, theList);
- }
- SetPt(&theCell, 0, 0);
- LSetCell(&twistDownHandle, sizeof twistDownHandle, theCell, theList);
- BuildVisibleList(theList, 0);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * BuildVisibleList
- *
- * BuildVisibleList is called when the list is created, or when the user clicks
- * on a twist-down button. selectedRow is the first row that needs to be redrawn.
- * To rebuild the entire list, store the list head into cell [0, 0] and call
- * with selectedRow = 0. Note: because people can be rebuilding the list from
- * within a larger sublist context, the list head must be stored in cell [0, 0].
- */
- pascal void
- BuildVisibleList(
- ListHandle theList,
- short selectedRow
- )
- {
- short nRows; /* How many we need to show */
- short currentRows; /* How many are in the list */
- Rect viewRect;
- TwistDownHdl listHead;
- Cell theCell;
-
- LSetDrawingMode(FALSE, theList);
- SetPt(&theCell, 0, 0); /* Get the list head */
- listHead = GetTwistDownElementHandle(theList, theCell);
- ClearSelectedElementBit(listHead);
- CopySelectionStateToList(theList, selectedRow);
- nRows = CountVisibleElements(listHead);
- currentRows = LIST.dataBounds.bottom;
- if (currentRows > nRows)
- LDelRow(currentRows - nRows, nRows, theList); /* Shrink the list */
- else if (currentRows < nRows)
- LAddRow(nRows - currentRows, currentRows + 1, theList); /* Grow it */
- if (nRows != 0)
- SetElementsInList(theList, listHead, &theCell);
- LSetDrawingMode(TRUE, theList);
- /*
- * Redraw any elements that are greater than the inserted row.
- */
- viewRect = LIST.rView;
- viewRect.top += ((selectedRow + 1) - LIST.visible.top)
- * LIST.cellSize.v;
- if (viewRect.top < viewRect.bottom)
- InvalRect(&viewRect);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CountVisibleElements
- *
- * CountVisibleElements is a recursive function that returns the number of visible
- * elements in the list. Call with the list head to process the entire list. Note:
- * list elements are visible, but sub-lists are visible only if the button state
- * requests visiblity. This function may be called with a NULL argument without
- * problems. Also note that we ignore the current List Manager cell data, working
- * only with the actual linked list structure. By convention, however, the list
- * head is stored in cell [0, 0].
- */
- static short
- CountVisibleElements(
- TwistDownHdl twistDownHandle
- )
- {
- short result;
-
- result = 0;
- while (twistDownHandle != NULL) {
- ++result;
- if (TestTDFlag(twistDownHandle, kShowSublist))
- result += CountVisibleElements((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CountListElements
- *
- * CountListElements returns the number of elements in the list: it counts all
- * elements in all sub-lists, visible or not.
- */
- pascal unsigned long
- CountListElements(
- TwistDownHdl twistDownHandle
- )
- {
- unsigned long result;
-
- result = 0;
- while (twistDownHandle != NULL) {
- ++result;
- if ((**twistDownHandle).subElement != NULL)
- result += CountVisibleElements((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ClearSelectedElementBit
- *
- * When we expand or contract the visible list, the relationship between the
- * visual display and the actual data changes. In particular, the selected cell
- * may contain different data. To resolve this dilemna, we copy the current
- * selection from the display list to the data elements, then copy the information
- * back when the new elements are inserted into the list. This is a three-step
- * process. First, we clear out the kSelectedElement bit from the linked list.
- */
- static void
- ClearSelectedElementBit(
- TwistDownHdl twistDownHandle
- )
- {
- while (twistDownHandle != NULL) {
- ClearTDFlag(twistDownHandle, kSelectedElement);
- if ((**twistDownHandle).subElement != NULL)
- ClearSelectedElementBit((**twistDownHandle).subElement);
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * CopySelectionStateToList
- *
- * After clearing the kSelectedElementBit from the element list, we save the
- * "is selected" bit from the display list into the associated element. Note that
- * the cell will be deselected but selection state is restored when the list is
- * rebuilt. The function starts with the row after the selected (clicked on) cell
- * as that cell is not redrawn and, presumably, is correctly hilited.
- */
- static void
- CopySelectionStateToList(
- ListHandle theList,
- short selectedRow
- )
- {
- TwistDownHdl twistDownHandle;
- Cell theCell;
-
- SetPt(&theCell, 0, selectedRow + 1);
- while (LGetSelect(TRUE, &theCell, theList)) {
- twistDownHandle = GetTwistDownElementHandle(theList, theCell);
- if (twistDownHandle != NULL)
- SetTDFlag(twistDownHandle, kSelectedElement);
- LSetSelect(FALSE, theCell, theList);
- ++theCell.v;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * SetElementsInList
- *
- * This is a recursive function that copies visible list elements from the linked
- * list to the List Manager list. The List Manager list was extended so it holds
- * all visible cells. Note that currentCell is a "global" that always contains
- * the current List Manager cell. To process the entire list, set currentCell to
- * [0, 0] and call with the list head. If the list element is selected (from
- * CopySelectionStateToList above), select this list cell.
- */
- static void
- SetElementsInList(
- ListHandle theList,
- TwistDownHdl twistDownHandle,
- Cell *currentCell
- )
- {
- while (twistDownHandle != NULL) {
- LSetCell(
- &twistDownHandle, sizeof twistDownHandle, *currentCell, theList);
- if (TestTDFlag(twistDownHandle, kSelectedElement))
- LSetSelect(TRUE, *currentCell, theList);
- ++currentCell->v;
- if (TestTDFlag(twistDownHandle, kShowSublist)) {
- SetElementsInList(
- theList,
- (**twistDownHandle).subElement,
- currentCell
- );
- }
- twistDownHandle = (**twistDownHandle).nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * GetTwistDownElementHandle
- *
- * GetTwistDownElementHandle returns the TwistDownHdl that is stored in a List
- * cell. It will return NULL if the cell is out of bounds.
- */
- pascal TwistDownHdl
- GetTwistDownElementHandle(
- ListHandle theList,
- Cell theCell
- )
- {
- TwistDownHdl twistDownHandle;
- short dataSize;
-
- dataSize = sizeof twistDownHandle;
- LGetCell(&twistDownHandle, &dataSize, theCell, theList);
- if (dataSize != sizeof twistDownHandle)
- twistDownHandle = NULL;
- return (twistDownHandle);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * NewTwistDownSiblingSet
- *
- * Initialize a sibling set.
- */
- pascal void
- NewTwistDownSiblingSet(
- TwistDownSiblingSetPtr twistDownSiblingSetPtr
- )
- {
- SIBLING.thisElement = NULL;
- SIBLING.firstElement = NULL;
- SIBLING.previousElement = NULL;
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * MakeTwistDownSibling
- *
- * Add a new element (by calling MakeTwistDownElement) to the end of the current
- * linked list. Also, handle the bookkeeping needed for the first element.
- */
- pascal OSErr
- MakeTwistDownSibling(
- TwistDownSiblingSetPtr twistDownSiblingSetPtr,
- short indentLevel,
- unsigned short dataLength,
- const Ptr dataPtr
- )
- {
- OSErr status;
-
- status = MakeTwistDownElement(
- SIBLING.previousElement,
- indentLevel,
- dataLength,
- dataPtr,
- &SIBLING.thisElement
- );
- if (status == noErr) {
- SIBLING.previousElement = SIBLING.thisElement;
- if (SIBLING.firstElement == NULL)
- SIBLING.firstElement = SIBLING.thisElement;
- }
- return (status);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * MakeTwistDownElement
- *
- * Adds an element to a linked list. It is designed to create elements in a
- * hierarchical linked-list where each element may be followed by a successor
- * (on the same "level") and/or by a child list (on a lower "level"). Note that
- * MakeTwistDownElement is only concerned with the linked list. It does not
- * change the List Manager list.
- *
- * The parameters are as follows:
- * previousElement
- * This is a handle to the predecessor to this element. previousElement is
- * NULL If this is the first element in a list or the first on a level.
- * indentLevel
- * This is the indentation-level of this list element. It is only
- * used to tab the list elements on the visual display. If you don't
- * want tabbing, set tabIndent to zero when the list was created.
- * dataLength
- * This is the length of the list element datum.
- * dataPtr
- * This is a pointer to the first byte of the list element datum. If NULL,
- * a data block of the requisite size will be created, but the caller is
- * responsible for filling it in.
- * result
- * If MakeElement succeeds, result will will contain a handle to the
- * list element it created. This is needed to create a successor
- * element.
- */
- pascal OSErr
- MakeTwistDownElement(
- TwistDownHdl previousElement,
- short indentLevel,
- unsigned short dataLength,
- const Ptr dataPtr,
- TwistDownHdl *result
- )
- {
- TwistDownHdl twistDownHandle;
-
- twistDownHandle = (TwistDownHdl) NewHandle(
- sizeof (TwistDownRecord)
- - sizeof (unsigned char)
- + dataLength
- );
- if (twistDownHandle != NULL) {
- if (previousElement != NULL)
- (**previousElement).nextElement = twistDownHandle;
- (**twistDownHandle).nextElement = NULL;
- (**twistDownHandle).subElement = NULL;
- (**twistDownHandle).flag = 0;
- (**twistDownHandle).indentLevel = indentLevel;
- (**twistDownHandle).dataLength = dataLength;
- if (dataPtr != NULL)
- BlockMoveData(dataPtr, (**twistDownHandle).data, dataLength);
- *result = twistDownHandle;
- }
- return (MemError());
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DisposeTwistDownHdl
- *
- * Dispose of the linked list that has the argument at its head, and of all
- * sublists linked to this list. Note that it presumes that the list element does
- * not, itself, contain Ptr or Handle data that must be disposed. The userProc,
- * if provided, will be called on each element.
- */
- pascal void
- DisposeTwistDownHdl(
- TwistDownHdl twistDownHandle,
- DisposeTwistDownCallback userProc,
- void *userData
- )
- {
- TwistDownHdl nextElement;
- TwistDownHdl subElement;
-
- while (twistDownHandle != NULL) {
- nextElement = (**twistDownHandle).nextElement;
- subElement = (**twistDownHandle).subElement;
- if (userProc != NULL)
- (*userProc)(twistDownHandle, userData);
- DisposeHandle((Handle) twistDownHandle);
- if (subElement != NULL)
- DisposeTwistDownHdl(subElement, userProc, userData);
- twistDownHandle = nextElement;
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * AppendTwistDownList
- *
- * Append the second list to the first list. Return the first list.
- */
- pascal TwistDownHdl
- AppendTwistDownList(
- TwistDownHdl dstTwistDownHdl,
- TwistDownHdl srcTwistDownHdl
- )
- {
- TwistDownHdl result;
-
- if ((result = dstTwistDownHdl) == NULL)
- result = srcTwistDownHdl;
- else {
- while ((**dstTwistDownHdl).nextElement != NULL)
- dstTwistDownHdl = (**dstTwistDownHdl).nextElement;
- (**dstTwistDownHdl).nextElement = srcTwistDownHdl;
- }
- return (result);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * TwistDownLDEF
- *
- * Draw the twist-down list cell. Note that we have to draw the expansion button
- * in one of five states: (expanded/compressed), (normal/selected), and an
- * animation state.
- */
- pascal void
- TwistDownLDEF(
- short listMessage,
- Boolean listSelect,
- Rect *listRect,
- Cell listCell,
- short listDataOffset,
- short listDataLen,
- ListHandle theList
- )
- {
- short indent;
- TwistDownHdl twistDownHandle;
- register TwistDownPtr twistDownPtr;
- GrafPtr grafPtr;
- short saveFontNumber;
- short saveFontSize;
- short cellSize;
- PolyHandle polyHandle;
- Point polyPoint;
- Rect viewRect;
- signed char elementLockState;
- Boolean onlyRedrawButton;
- FontInfo info;
- #define TestFlag(flagBit) (((*twistDownPtr).flag & (flagBit)) != 0)
-
- UNUSED(listDataOffset);
- switch (listMessage) {
- case lInitMsg:
- /*
- * Initialize the list indentation values. Since the userHandle
- * (which has the TwistDown private data) hasn't been setup yet,
- * we let the current font and font size establish the indentation.
- */
- LIST.indent.h = 4;
- GetFontInfo(&info);
- LIST.indent.v = info.ascent;
- break;
- case lCloseMsg:
- if (LIST.userHandle != NULL) {
- ForgetPoly(PRIVATE.openTriangle);
- ForgetPoly(PRIVATE.closedTriangle);
- ForgetPoly(PRIVATE.intermediateTriangle);
- DisposeHandle(LIST.userHandle);
- LIST.userHandle = NULL;
- }
- break;
- case lDrawMsg:
- onlyRedrawButton = FALSE;
- if (listDataLen > 0 && LIST.userHandle != NULL) {
- /*
- * Get the cell content. This is the handle that has the list
- * element. We check that the userHandle has been setup correctly.
- * Note that we don't use LFind (or similar) because the data
- * might not be aligned in the list cell storage. Actually, the
- * data is aligned as we only store Handles in the cells.
- */
- cellSize = sizeof twistDownHandle;
- LGetCell(&twistDownHandle, &cellSize, listCell, theList);
- if (cellSize == sizeof twistDownHandle
- && twistDownHandle != NULL) {
- elementLockState = HGetState((Handle) twistDownHandle);
- HLock((Handle) twistDownHandle);
- twistDownPtr = (*twistDownHandle);
- onlyRedrawButton = TestFlag(kOnlyRedrawButton);
- viewRect = *listRect;
- if (onlyRedrawButton) {
- if (PRIVATE.isLeftJustify) {
- viewRect.right = viewRect.left
- + LIST.indent.h
- + PRIVATE.triangleWidth;
- }
- else {
- viewRect.left = viewRect.right
- - LIST.indent.h
- - kTriangleOutsideGap
- - PRIVATE.triangleWidth;
- }
- }
- if (onlyRedrawButton == FALSE || TestFlag(kEraseButtonArea))
- EraseRect(&viewRect);
- if (TestFlag(kHasTwistDown)) {
- /*
- * Draw the expansion triangle in one of
- * its three states.
- */
- polyPoint.v = listRect->top + 1;
- if (PRIVATE.isLeftJustify) {
- polyPoint.h = listRect->left
- + LIST.indent.h
- + kTriangleOutsideGap;
- }
- else {
- polyPoint.h = listRect->right
- - LIST.indent.h
- - PRIVATE.triangleWidth
- + kTriangleInsideGap;
- }
- if (TestFlag(kDrawIntermediate))
- polyHandle = PRIVATE.intermediateTriangle;
- else if (TestFlag(kShowSublist))
- polyHandle = PRIVATE.openTriangle;
- else {
- polyHandle = PRIVATE.closedTriangle;
- }
- DrawTriangle(
- polyHandle,
- polyPoint,
- TestFlag(kDrawButtonFilled)
- );
- }
- if (onlyRedrawButton == FALSE
- && (*twistDownPtr).dataLength > 0) {
- /*
- * Indent the text to show the depth of the hierarchy.
- */
- indent = LIST.indent.h
- + PRIVATE.triangleWidth
- + (PRIVATE.tabIndent * (*twistDownPtr).indentLevel);
- viewRect = *listRect;
- /*
- * Build a display rectangle for the cell text and set the
- * pen to the leftmost position of the text. Note that
- * this right-justifies text for Arabic and Hebrew.
- */
- if (PRIVATE.isLeftJustify)
- viewRect.left += indent;
- else {
- viewRect.right -= indent;
- }
- GetPort(&grafPtr);
- saveFontNumber = grafPtr->txFont;
- saveFontSize = grafPtr->txSize;
- TextFont(PRIVATE.fontNumber);
- TextSize(PRIVATE.fontSize);
- (PRIVATE.drawProc)(theList, twistDownPtr, &viewRect);
- TextFont(saveFontNumber);
- TextSize(saveFontSize);
- } /* Drawing cell */
- HSetState((Handle) twistDownHandle, elementLockState);
- } /* Have list element */
- } /* Have cell data */
- if (listSelect == FALSE || onlyRedrawButton)
- break;
- /* Continue to do hilite */
- case lHiliteMsg:
- if (PRIVATE.canHiliteSelection) {
- LMSetHiliteMode(LMGetHiliteMode() & ~(1 << hiliteBit));
- viewRect = *listRect;
- if (PRIVATE.isLeftJustify)
- viewRect.left += (LIST.indent.h * PRIVATE.triangleWidth);
- else {
- viewRect.right -= (LIST.indent.h * PRIVATE.triangleWidth);
- }
- InvertRect(&viewRect);
- }
- break;
- }
- #undef TestFlag
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DrawTriangle
- *
- * Draw the polygon and fill it so it looks a bit like the Finder. If isSelected
- * is TRUE, the polygon is always black-filled. Else, on color monitors, a light
- * gray is chosen.
- *
- * DrawTriangle uses the DeviceLoop available with System 7 to draw across
- * multiple screens.
- */
-
- typedef struct TriangleInfo {
- PolyHandle polyHandle;
- Point polyPoint;
- } TriangleInfo, *TriangleInfoPtr;
-
- static pascal void DrawThisTriangle(
- short depth,
- short deviceFlags,
- GDHandle targetDevice,
- TriangleInfoPtr triangleInfoPtr
- );
-
- static void
- DrawTriangle(
- PolyHandle polyHandle,
- Point polyPoint,
- Boolean isSelected
- )
- {
- TriangleInfo triangleInfo;
- RgnHandle drawingRgn;
- long savedA5;
- static DeviceLoopDrawingUPP drawingProcUPP;
- /*
- * Refresh A5 so we can use the QuickDraw globals, even if we're called
- * in a non-application context.
- */
- savedA5 = SetCurrentA5();
- triangleInfo.polyHandle = polyHandle;
- triangleInfo.polyPoint = polyPoint;
- OffsetPoly(polyHandle, polyPoint.h, polyPoint.v);
- if (isSelected)
- FillPoly(polyHandle, &qd.black);
- else {
- drawingRgn = NewRgn();
- OpenRgn();
- FramePoly(polyHandle);
- CloseRgn(drawingRgn);
- if (drawingProcUPP == NULL)
- drawingProcUPP = NewDeviceLoopDrawingProc(DrawThisTriangle);
- DeviceLoop(drawingRgn, drawingProcUPP, (long) &triangleInfo, 0);
- DisposeRgn(drawingRgn);
- }
- /*
- * A thicker pen might look better for large font sizes, but it needs
- * to be done in a way that looks good for both button orientations.
- */
- FramePoly(polyHandle);
- OffsetPoly(polyHandle, -polyPoint.h, -polyPoint.v);
- SetA5(savedA5);
- }
-
- static pascal void
- DrawThisTriangle(
- short depth,
- short deviceFlags,
- GDHandle targetDevice,
- TriangleInfoPtr triangleInfoPtr
- )
- {
- RGBColor foreColor;
- RGBColor saveForeColor;
- RGBColor backColor;
- short i;
- Rect polyRect;
- #define TRI (*triangleInfoPtr)
-
- UNUSED(deviceFlags);
- UNUSED(targetDevice);
- polyRect = (**TRI.polyHandle).polyBBox;
- LocalToGlobal(& ((Point *) &polyRect)[0]);
- LocalToGlobal(& ((Point *) &polyRect)[1]);
- if (depth > 1) {
- /*
- * We are drawing on a color device (or devices). Fill the unselected
- * triangle with a very light gray. The Finder extends this by filling
- * using the icon color instead of black.
- */
- GetForeColor(&foreColor);
- saveForeColor = foreColor;
- GetBackColor(&backColor);
- /*
- * This loop sets foreColor to a very light gray.
- */
- for (i = 0; i < 8; i++) {
- if (GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
- break;
- }
- RGBForeColor(&foreColor);
- FillPoly(TRI.polyHandle, &qd.black);
- RGBForeColor(&saveForeColor);
- }
- else {
- #if MONOCHROME_FILL
- /*
- * We really don't need to do this, but it was useful in debugging
- * the algorithm on a machine with multiple displays. This fills
- * the polygon with a light gray texture on monochrome displays.
- * This is different from the Finder algorithm.
- */
- FillPoly(TRI.polyHandle, (ConstPatternParam) &qd.ltGray);
- #else
- /*
- * Normally, we need only erase the interior of the polygon.
- */
- ErasePoly(TRI.polyHandle);
- #endif
- #undef TRI
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * SortTwistDownList
- *
- * Sort a list according to the comparison function. Algorithm adapted from
- * Numerical Recipes in C (Press, Flannery, Teukolsky, & Vetterling),
- * ISBN 0-521-35465-X. Algorithm 8.2 "Heapsort"
- *
- * This is *not* a recursive procedure. It allocates a temporary array that holds
- * a oopy of the list. The algorithm, then, is:
- * 1. Count the number of elements in the list - don't count sublists, though.
- * 2. Allocate a vector to hold the list handles and copy the list handles
- * into the vector.
- * 3. Heapsort.
- * 4. The "Promotion" phase creates a new list set.
- * The function returns noErr on success, or a memory allocation error. By using
- * a non-recursive function, we avoid a problem where an ordered input list chews
- * up the application stack, which could lead to bizarre crashes.
- */
- pascal OSErr
- SortTwistDownList(
- TwistDownHdl *twistDownHdlPtr,
- TwistDownSortCompareProc sortCompareProc,
- void *refCon
- )
- {
- OSErr status;
- unsigned long nElements;
- unsigned long i;
- unsigned long j;
- unsigned long siftIndex;
- unsigned long lastIndex;
- TwistDownHdl twistDownHdl;
- TwistDownHdl *heapVector; /* One-origin vector */
-
- nElements = 0;
- status = noErr;
- for (twistDownHdl = *twistDownHdlPtr; /* Count list elements */
- twistDownHdl != NULL;
- twistDownHdl = (**twistDownHdl).nextElement) {
- ++nElements;
- }
- if (nElements > 1) { /* 0 or 1 elements aren't sorted */
- heapVector = (TwistDownHdl *)
- NewPtr((nElements + 1) * sizeof (TwistDownHdl));
- if (heapVector == NULL)
- status = MemError();
- else {
- /*
- * Copy the list into the vector. Note that the vector is one-
- * origin (this prevents an infinite loop if the index is zero).
- */
- for (i = 1, twistDownHdl = *twistDownHdlPtr;
- twistDownHdl != NULL;
- twistDownHdl = (**twistDownHdl).nextElement) {
- heapVector[i++] = twistDownHdl;
- }
- siftIndex = (nElements >> 1) + 1;
- lastIndex = nElements;
- for (;;) {
- if (siftIndex > 1) /* Still in hiring phase? */
- twistDownHdl = heapVector[--siftIndex];
- else { /* Else, in promotion phase? */
- /*
- * Clear a space at the end of the vector, retire the
- * top of the heap into it, if we're done with the
- * last promotion, store the "least competent"
- * worker, and exit.
- */
- twistDownHdl = heapVector[lastIndex];
- heapVector[lastIndex] = heapVector[1];
- if (--lastIndex <= 1) {
- heapVector[1] = twistDownHdl;
- break; /* Exit the for (;;) loop */
- }
- }
- /*
- * Whether we are in the hiring or promotion phase, we
- * continue here to sift the current element into its
- * proper position.
- */
- i = siftIndex;
- j = siftIndex << 1;
- while (j <= lastIndex) {
- if (j < lastIndex
- && (*sortCompareProc)(
- refCon,
- heapVector[j],
- heapVector[j + 1]
- ) < 0) {
- ++j;
- }
- if ((*sortCompareProc)(
- refCon, twistDownHdl, heapVector[j]
- ) < 0) {
- heapVector[i] = heapVector[j];
- i = j;
- j += i;
- }
- else {
- break;
- }
- }
- heapVector[i] = twistDownHdl;
- }
- /*
- * heapVector has the sorted list. Build the result list.
- */
- *twistDownHdlPtr = heapVector[1];
- for (i = 2; i <= nElements; i++)
- (**(heapVector[i - 1])).nextElement = heapVector[i];
- (**(heapVector[nElements])).nextElement = NULL;
- DisposePtr((Ptr) heapVector);
- }
- }
- return (status);
-
- }
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * AdjustHorizontalScrollbar
- *
- * This is called when the user clicks in the horizontal scrollbar that was
- * created when the list was created. Note that we handle the scrollbar here,
- * as it is not stored within the ListManager list record.
- */
- static void
- AdjustHorizontalScrollbar(
- ListHandle theList
- )
- {
- short horizontalMax;
- ControlHandle theControl;
-
- theControl = PRIVATE.hScroll;
- horizontalMax = kMaxHorizontalScroll - width((**theControl).contrlRect);
- if (horizontalMax < 0)
- horizontalMax = 0;
- SetControlMinimum(theControl, 0);
- SetControlMaximum(theControl, horizontalMax);
- HiliteControl(
- theControl,
- (horizontalMax == 0) ? kDisabledControl : kActiveControl
- );
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ScrollTwistDownActionProc
- *
- * This is called by TrackControl when the user clicks in the horizontal
- * scrollbar that was created when the list was created. Note that we handle
- * the scrollbar here, as it is not stored within the ListManager list record.
- */
- static pascal void
- ScrollTwistDownActionProc(
- register ControlHandle theControl,
- short partCode
- )
- {
- short delta;
-
- delta = (width((**theControl).contrlRect) * 7) / 8;
- switch (partCode) {
- case kInUpButtonControlPart: delta = -CharWidth('M'); break;
- case kInPageUpControlPart: delta = (-delta); break;
- case kInDownButtonControlPart: delta = CharWidth('M'); break;
- case kInPageDownControlPart: /* All set */ break;
- default: return; /* Mouse exited control */
- }
- SetControlValue(theControl, GetControlValue(theControl) + delta);
- ScrollTwistDownList(theControl);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * ScrollTwistDownList
- *
- * This is called when the user clicks in the horizontal scrollbar that was
- * created when the list was created. This is the function that modifies the
- * scrollbar.
- */
- static void
- ScrollTwistDownList(
- register ControlHandle theControl
- )
- {
- ListHandle theList;
- short delta;
- RgnHandle clipRgn;
- RgnHandle updateRgn;
- Rect viewRect;
-
- theList = (ListHandle) GetControlReference(theControl);
- /*
- * LIST.indent.h is negative when the cell is scrolled left. Get its
- * current amount (as a positive value) and set delta to the amount
- * that must be scrolled. Delta will be positive to scroll right
- * (meaning that the scroll bar moved left). This probably won't
- * work for a right-to-left language.
- */
- delta = kZeroIndent - LIST.indent.h - GetControlValue(theControl);
- if (delta != 0) {
- /*
- * We must scroll the list. Get a clip rectangle so the scrolling
- * is limited to the drawing area, scroll it, and update anything
- * that came into view. Hmm, should the buttons ever scroll?
- */
- viewRect = LIST.rView;
- clipRgn = NewRgn();
- updateRgn = NewRgn();
- GetClip(clipRgn);
- ClipRect(&viewRect);
- ScrollRect(&viewRect, delta, 0, updateRgn);
- LIST.indent.h += delta;
- LUpdate(updateRgn, theList);
- SetClip(clipRgn);
- DisposeRgn(updateRgn);
- DisposeRgn(clipRgn);
- }
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Print Manager
- *
- * The following section lets the user print a twistdown list.
- */
-
- static pascal OSErr DefaultTwistDownPrintSetupProc(
- ListHandle theList,
- THPrint hPrint,
- void *clientData,
- StringPtr dateString
- );
- static pascal OSErr DefaultTwistDownPrintImageProc(
- ListHandle theList,
- THPrint hPrint,
- void *clientData,
- StringPtr dateString,
- const Rect *pageRect,
- short pageNumber
- );
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * PrintTwistDownList
- *
- * Mainline print handler. This calls task-specific routines to count the number
- * of pages and to image each page. The caller may provide special routines for
- * these tasks, or use the default calls (that presume text-only list elements).
- * This is modified from Rich Siegel's generic print driver.
- */
- pascal OSErr
- PrintTwistDownList(
- ListHandle theList,
- THPrint *printHandlePtr,
- Boolean doStyleDialog,
- TwistDownPrintSetupProc twistDownPrintSetupProc,
- TwistDownPrintImageProc twistDownPrintImageProc,
- TwistDownPrintExitProc twistDownPrintExitProc,
- void *clientData
- )
- {
- OSErr status; /* Current error */
- THPrint hPrint; /* Copy of *printHandlePtr */
- Boolean printIsOpen; /* PROpen ... PRClose */
- Boolean docIsOpen; /* PROpenDoc ... PRCloseDoc */
- short nCopies; /* Number of copies to print */
- short iCopy; /* Which copy are we printing? */
- short pageNumber; /* Which page are we printing? */
- Rect pageRect; /* Current page image rectangle */
- short printDevice; /* What kind of printer? */
- Boolean draftMode; /* Draft or spool? */
- TPPrPort printPort; /* The print port */
- TPrStatus printStatus; /* PrPicFile status info */
- GrafPtr savePort; /* Old GrafPort */
- unsigned long now; /* When we're called */
- Str255 dateString; /* Handy date + time string */
- #ifndef bDevCItoh
- #define bDevCItoh 1
- #endif
- #ifndef bDevLaser
- #define bDevLaser 3
- #endif
- enum {
- kImageWriter = bDevCItoh,
- kLaserWriter = bDevLaser
- };
- /*
- * This macro exits the print handler on any error.
- */
- #define CheckError(stat) do { \
- if ((status = (stat)) != noErr) \
- goto exit; \
- } while (0)
- #if 1 /* Debug only */
- #define CHECK(stat, why) do { \
- OSErr checkStat; \
- extern short NonFatalError(OSErr, ConstStr255Param); \
- if ((checkStat = (stat)) != noErr) \
- NonFatalError(checkStat, why); \
- } while (0)
- #else
- #define CHECK(stat, why) /* Nothing */
- #endif
-
- GetPort(&savePort);
- status = noErr;
- printIsOpen = FALSE;
- docIsOpen = FALSE;
- if (twistDownPrintSetupProc == NULL)
- twistDownPrintSetupProc = DefaultTwistDownPrintSetupProc;
- if (twistDownPrintImageProc == NULL)
- twistDownPrintImageProc = DefaultTwistDownPrintImageProc;
- /*
- * Set the date and time
- */
- GetDateTime(&now);
- IUDateString(now, abbrevDate, dateString);
- dateString[++dateString[0]] = ' ';
- dateString[++dateString[0]] = '/';
- iCopy = ++dateString[0];
- IUTimeString(now, FALSE, &dateString[iCopy]);
- dateString[0] += (dateString[iCopy] + 1);
- dateString[iCopy] = ' ';
- /*
- * If there is no Print Handle, allocate one and fail on errors. On exit,
- * the print handler is retained so that multiple printouts retain the
- * same user selections.
- */
- PrOpen();
- status = PrError();
- CHECK(status, "\pCan't open print manager");
- CheckError(status);
- printIsOpen = TRUE;
- if (*printHandlePtr == NULL) {
- *printHandlePtr = (THPrint) NewHandle(sizeof (TPrint));
- if (*printHandlePtr == NULL) {
- status = MemError();
- CHECK(status, "\pNewHandle(sizeof (TPrint)) failed");
- goto exit;
- }
- PrintDefault(*printHandlePtr);
- }
- hPrint = (*printHandlePtr);
- /*
- * Validate the Print Handle and call the Print Style dialog if necessary.
- * If the user cancels, exit (noErr). Then call the job dialog to get the
- * number of copies (exit on cancel here, too). Note that we don't exit
- * with userCanceledErr, as this is merely informative.
- */
- if (PrValidate(hPrint) || doStyleDialog) {
- if (PrStlDialog(hPrint) == FALSE) {
- CHECK(userCanceledErr, "\pUser canceled style dialog");
- goto exit;
- }
- }
- if (PrJobDialog(hPrint) == FALSE) {
- CHECK(userCanceledErr, "\pUser canceled job dialog");
- goto exit;
- }
- /*
- * Setup is done, call the user's setup procedure and exit on errors.
- * The setup function must set the number of pages in the document and
- * return noErr to continue.
- */
- SetCursor(*GetCursor(watchCursor));
- status = (*twistDownPrintSetupProc)
- (theList, hPrint, clientData, dateString);
- CHECK(status, "\pUser setup function failed");
- if (status != noErr)
- goto exit;
- /*
- * Grab some information for the loops that follow.
- * printDevice is the printing device. This is only interesting for
- * ImageWriter (StyleWriter?) compatibility, and hasn't been tested
- * in many years.
- */
- printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
- draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
- if (draftMode && printDevice == kImageWriter)
- nCopies = (**hPrint).prJob.iCopies;
- else {
- nCopies = 1;
- }
- /*
- * Printing begins here.
- */
- printPort = PrOpenDoc(hPrint, NULL, NULL);
- docIsOpen = TRUE;
- status = PrError();
- CHECK(status, "\pCan't open document to print");
- CheckError(status);
- /*
- * Note: if you select "Print to EPS file", you can only image
- * one page, the first in this case.
- */
- for (iCopy = 1; iCopy <= nCopies; iCopy++) {
- for (pageNumber = (**hPrint).prJob.iFstPage;
- pageNumber <= (**hPrint).prJob.iLstPage;
- pageNumber++) {
- SetCursor(*GetCursor(watchCursor));
- PrOpenPage(printPort, NULL);
- status = PrError();
- if (status == noErr) {
- pageRect = (**hPrint).prInfo.rPage;
- status = (*twistDownPrintImageProc)(
- theList,
- hPrint,
- clientData,
- dateString,
- &pageRect,
- pageNumber
- );
- }
- PrClosePage(printPort);
- if (status == noErr)
- status = PrError();
- CHECK(status, "\pPrint page error");
- CheckError(status);
- }
- }
- /*
- * Normal exit
- */
- SetPort(savePort);
- PrCloseDoc(printPort);
- docIsOpen = FALSE;
- status = PrError();
- CHECK(status, "\pCan't close document after printing complete");
- CheckError(status);
- if (draftMode == FALSE) {
- PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
- status = PrError();
- CHECK(status, "\pCan't spool document after printing complete");
- }
- /*
- * Everyone exits here.
- */
- exit: SetPort(savePort);
- if (docIsOpen)
- PrCloseDoc(printPort);
- if (printIsOpen)
- PrClose();
- if (twistDownPrintExitProc != NULL)
- status = (*twistDownPrintExitProc)(theList, clientData, status);
- InitCursor();
- return (status);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DefaultTwistDownPrintSetupProc
- *
- * This will be called if the user didn't provide a print setup procedure. Our
- * default output page will have the date on the top-left and the page number on
- * the top right (both in 9 pt Helvetica Bold). This function must set the actual
- * number of pages that are to be printed.
- *
- * Note: the print functions have not been upgraded for right-to-left languages.
- */
- static pascal OSErr
- DefaultTwistDownPrintSetupProc(
- ListHandle theList,
- THPrint hPrint,
- void *clientData,
- StringPtr dateString
- )
- {
- unsigned short lineHeight;
- unsigned short linesInPrintout;
- unsigned short linesPerPage;
- unsigned short nPages;
- unsigned short headerHeight;
- Rect printRect;
- FontInfo info;
- short fontNumber;
-
- UNUSED(clientData);
- UNUSED(dateString);
- SetPort(LIST.port);
- GetFNum(kPrintoutHeaderFont, &fontNumber);
- TextFont(fontNumber);
- TextSize(kPrintoutHeaderFontSize);
- GetFontInfo(&info);
- headerHeight = info.ascent + info.descent + info.leading;
- /*
- * Get the display page rectangle, remove the header, and determine
- * the number of lines that will fit on one page and, from that,
- * the number of pages in the printout. If this is less than the
- * default (999), adjust the print record.
- */
- printRect = (**hPrint).prInfo.rPage;
- printRect.top += (headerHeight + kPrintoutHeaderGap);
- linesInPrintout = height(LIST.dataBounds);
- TextFont(PRIVATE.fontNumber);
- TextSize(PRIVATE.fontSize);
- TextFace(normal);
- GetFontInfo(&info);
- lineHeight = info.ascent + info.descent + info.leading;
- linesPerPage = height(printRect) / lineHeight;
- nPages = (linesInPrintout + linesPerPage - 1) / linesPerPage;
- /*
- * We want to print nPages. Set this into the print record if the
- * caller asked for more (i.e. for the default "all pages").
- */
- if ((**hPrint).prJob.iLstPage > nPages)
- (**hPrint).prJob.iLstPage = nPages;
- return (noErr);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DefaultTwistDownPrintImageProc
- *
- * Image the current page. This must duplicate some of the code from the
- * print setup function (above). This needs work for right-to-left languages.
- *
- * The page is imaged using the currently selected display font and font size.
- */
- static pascal OSErr
- DefaultTwistDownPrintImageProc(
- ListHandle theList,
- THPrint hPrint,
- void *clientData,
- StringPtr dateString,
- const Rect *pageRect,
- short pageNumber
- )
- {
- unsigned short i;
- unsigned short lastRow;
- unsigned short linesPerPage;
- unsigned short headerHeight;
- Rect printRect;
- short lineHeight;
- Cell listCell;
- FontInfo info;
- short fontNumber;
- Str255 work;
- short cellSize;
- TwistDownHdl twistDownHandle;
- register TwistDownPtr twistDownPtr;
- signed char elementLockState;
-
- UNUSED(clientData);
- UNUSED(hPrint);
- /*
- * First, image the header.
- */
- GetFNum(kPrintoutHeaderFont, &fontNumber);
- TextFont(fontNumber);
- TextSize(kPrintoutHeaderFontSize);
- TextFace(kPrintoutHeaderStyle);
- GetFontInfo(&info);
- headerHeight = info.ascent + info.descent + info.leading;
- MoveTo(pageRect->left, pageRect->top + info.ascent);
- DrawString(dateString);
- pstrcpy(work, "\pPage: ");
- i = work[0];
- NumToString(pageNumber, &work[i]);
- work[0] += work[i];
- work[i] = ' ';
- MoveTo(pageRect->right - StringWidth(work), pageRect->top + info.ascent);
- DrawString(work);
- printRect = *pageRect;
- printRect.top += (headerHeight + kPrintoutHeaderGap);
- /*
- * Now, do the list data itself.
- */
- TextFont(PRIVATE.fontNumber);
- TextSize(PRIVATE.fontSize);
- TextFace(normal);
- GetFontInfo(&info);
- lineHeight = info.ascent + info.descent + info.leading;
- linesPerPage = height(printRect) / lineHeight;
- listCell.h = 0;
- listCell.v = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
- lastRow = listCell.v + linesPerPage;
- if (lastRow > LIST.dataBounds.bottom)
- lastRow = LIST.dataBounds.bottom;
- printRect.bottom = printRect.top + lineHeight;
- while (listCell.v < lastRow) {
- cellSize = sizeof twistDownHandle;
- LGetCell(&twistDownHandle, &cellSize, listCell, theList);
- if (cellSize == sizeof twistDownHandle && twistDownHandle != NULL) {
- elementLockState = HGetState((Handle) twistDownHandle);
- HLock((Handle) twistDownHandle);
- twistDownPtr = (*twistDownHandle);
- (PRIVATE.drawProc)(theList, twistDownPtr, &printRect);
- HSetState((Handle) twistDownHandle, elementLockState);
- }
- OffsetRect(&printRect, 0, lineHeight);
- ++listCell.v;
- }
- return (noErr);
- }
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * DefaultTwistDownDrawProc
- *
- * Draw the current list element in the viewRect. The caller has setup the font
- * information.
- */
- static pascal void
- DefaultTwistDownDrawProc(
- ListHandle theList, /* The list itself */
- TwistDownPtr twistDownPtr, /* Locked data handle */
- const Rect *viewRect /* Draw in this area */
- )
- {
- short textWidth;
- #define ELEM (*twistDownPtr)
-
- if (PRIVATE.isLeftJustify)
- MoveTo(viewRect->left, viewRect->top + LIST.indent.v);
- else {
- textWidth = TextWidth(ELEM.data, 0, ELEM.dataLength);
- MoveTo(viewRect->right - textWidth, viewRect->top + LIST.indent.v);
- }
- DrawText(ELEM.data, 0, ELEM.dataLength);
- #undef ELEM
- }
-
- static void
- pstrcpy(
- StringPtr dst,
- ConstStr255Param src
- )
- {
- BlockMoveData(src, dst, src[0] + 1);
- }
-
- static void
- pstrcat(
- StringPtr dst,
- ConstStr255Param src
- )
- {
- short length;
-
- length = 255 - dst[0];
- if (length > src[0])
- length = src[0];
- BlockMoveData(&src[1], &dst[1] + dst[0], length);
- dst[0] += length;
- }
-